package com.zqweb.gulimall.product.service.impl;

import com.alibaba.fastjson.TypeReference;
import com.zqweb.common.constant.ProductConstant;
import com.zqweb.common.es.SkuESModel;
import com.zqweb.common.to.BoundsTo;
import com.zqweb.common.to.SkuFullReductionTo;
import com.zqweb.common.to.SkuStockTo;
import com.zqweb.common.utils.R;
import com.zqweb.gulimall.product.entity.*;
import com.zqweb.gulimall.product.feign.CouponFeignService;
import com.zqweb.gulimall.product.feign.EsFeignService;
import com.zqweb.gulimall.product.feign.WareFeignService;
import com.zqweb.gulimall.product.service.*;
import com.zqweb.gulimall.product.vo.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zqweb.common.utils.PageUtils;
import com.zqweb.common.utils.Query;

import com.zqweb.gulimall.product.dao.SpuInfoDao;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;


@Service("spuInfoService")
@Slf4j
public class SpuInfoServiceImpl extends ServiceImpl<SpuInfoDao, SpuInfoEntity> implements SpuInfoService {
    @Autowired
    private SpuInfoDescService spuInfoDescService;
    @Autowired
    private SpuImagesService spuImagesService;
    @Autowired
    private ProductAttrValueService productAttrValueService;
    @Autowired
    private SkuInfoService skuInfoService;
    @Autowired
    private SkuImagesService skuImagesService;
    @Autowired
    private SkuSaleAttrValueService skuSaleAttrValueService;
    @Autowired
    private CouponFeignService couponFeignService;
    @Autowired
    private BrandService brandService;
    @Autowired
    private CategoryService categoryService;
    @Autowired
    private AttrService attrService;
    @Autowired
    private WareFeignService wareFeignService;
    @Autowired
    private EsFeignService esFeignService;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<SpuInfoEntity> page = this.page(
                new Query<SpuInfoEntity>().getPage(params),
                new QueryWrapper<SpuInfoEntity>()
        );

        return new PageUtils(page);
    }

    @Override
    // 带查询条件的过滤分页查询（分类id、品牌id、发布status、检索key）
    public PageUtils queryPageWithConditions(Map<String, Object> params) {
        QueryWrapper<SpuInfoEntity> queryWrapper = new QueryWrapper<>();
        String key = (String) params.get("key");
        if(!StringUtils.isEmpty(key)){
            queryWrapper.and((wrapper) -> {
                wrapper.like("id",key).or().like("spu_name",key);
            });
        }
        String catelogId = (String) params.get("catelogId");
        if(!StringUtils.isEmpty(catelogId) && !"0".equals(catelogId)){
            queryWrapper.eq("catalog_id",catelogId);
        }
        String brandId = (String) params.get("brandId");
        if(!StringUtils.isEmpty(brandId) && !"0".equals(brandId)){
            queryWrapper.eq("brand_id",brandId);
        }
        String status = (String) params.get("status");
        if(!StringUtils.isEmpty(status)){
            queryWrapper.eq("publish_status",status);
        }
        IPage<SpuInfoEntity> page = this.page(
                new Query<SpuInfoEntity>().getPage(params),
                queryWrapper
        );

        return new PageUtils(page);
    }

    @Override
    @Transactional // 注意：对于这种整体关联性比较强的大保存业务，需要开启事务支持，保证数据的一致性和完整性
    // 保存商品信息的完整数据SpuSaveVo
    public void save(SpuSaveVo spuSaveVo) {
        // 1、保存spu的基本信息 pms_spu_info
        SpuInfoEntity spuInfoEntity = new SpuInfoEntity();
        BeanUtils.copyProperties(spuSaveVo,spuInfoEntity);// 只会对拷相同属性，不同属性的则不对拷
        spuInfoEntity.setCreateTime(new Date());
        spuInfoEntity.setUpdateTime(new Date());
        this.save(spuInfoEntity);

        // 2、保存spu的信息描述 pms_spu_info_desc
        SpuInfoDescEntity spuInfoDescEntity = new SpuInfoDescEntity();
        spuInfoDescEntity.setSpuId(spuInfoEntity.getId());
        List<String> decript = spuSaveVo.getDecript();
        spuInfoDescEntity.setDecript(String.join(",", decript));// 将List的每个元素以逗号分割
        spuInfoDescService.save(spuInfoDescEntity);

        // 3、保存spu的图片集 pms_spu_images
        List<String> images = spuSaveVo.getImages();
        spuImagesService.saveImages(spuInfoEntity.getId(),images);

        // 4、保存spu的规格参数 pms_product_attr_value
        List<BaseAttrs> baseAttrs = spuSaveVo.getBaseAttrs();
        List<ProductAttrValueEntity> attrValueEntities = baseAttrs.stream().map(attr -> {
            ProductAttrValueEntity attrValueEntity = new ProductAttrValueEntity();
            attrValueEntity.setAttrId(attr.getAttrId());
            ProductAttrValueEntity valueEntity = productAttrValueService.getById(attr.getAttrId());
            attrValueEntity.setAttrName(valueEntity.getAttrName());
            attrValueEntity.setAttrValue(attr.getAttrValues());
            attrValueEntity.setSpuId(spuInfoEntity.getId());
            attrValueEntity.setQuickShow(attr.getShowDesc());
            return attrValueEntity;
        }).collect(Collectors.toList());
        productAttrValueService.saveAttrValues(attrValueEntities);

        // 5、保存spu的积分信息
        Bounds bounds = spuSaveVo.getBounds();
        BoundsTo boundsTo = new BoundsTo();
        BeanUtils.copyProperties(bounds,boundsTo);
        boundsTo.setSpuId(spuInfoEntity.getId());
        R saveBounds = couponFeignService.save(boundsTo);
        if(saveBounds.getCode() != 0){
            log.error("远程调用coupon微服务中spu的积分信息保存功能失败！");
        }


        /*
            6、保存当前spu对应的所有sku信息：
                1）sku的基本信息：pms_sku_info
                2）sku的图片信息：pms_sku_images
                3）sku的销售属性信息：pms_sku_sale_attr_value
                4）sku的优惠、满减等信息：gulimall_sms库的相关表
         */
        List<Skus> skusList = spuSaveVo.getSkus();
        if(skusList != null && skusList.size() > 0){
            skusList.forEach(item -> {
                String imgUrl = "";
                for (Images image : item.getImages()) {
                    if(image.getDefaultImg() == 1){
                        imgUrl = image.getImgUrl();
                    }
                }
                // 保存sku的基本信息
                SkuInfoEntity skuInfoEntity = new SkuInfoEntity();
                BeanUtils.copyProperties(item,skuInfoEntity);
                skuInfoEntity.setBrandId(spuInfoEntity.getBrandId());
                skuInfoEntity.setCatalogId(spuInfoEntity.getCatalogId());
                skuInfoEntity.setSaleCount(0L);
                skuInfoEntity.setSkuDefaultImg(imgUrl);
                skuInfoEntity.setSpuId(spuInfoEntity.getId());
                // 会将自增后的infoId回显给当前的skuInfoEntity
                skuInfoService.saveSkuInfo(skuInfoEntity);

                // 保存sku的图片信息
                List<SkuImagesEntity> skuImagesEntities = item.getImages().stream().map(img -> {
                    SkuImagesEntity skuImagesEntity = new SkuImagesEntity();
                    BeanUtils.copyProperties(img,skuImagesEntity);
                    skuImagesEntity.setSkuId(skuInfoEntity.getSkuId());
                    return skuImagesEntity;
                }).filter(image -> !StringUtils.isEmpty(image.getImgUrl())).collect(Collectors.toList());
                // TODO 没有图片路径的，就不要保存了！
                skuImagesService.saveBatch(skuImagesEntities);

                // 保存sku的销售属性信息
                List<SkuSaleAttrValueEntity> skuSaleAttrValueEntities = item.getAttr().stream().map(attr -> {
                    SkuSaleAttrValueEntity skuSaleAttrValueEntity = new SkuSaleAttrValueEntity();
                    BeanUtils.copyProperties(attr, skuSaleAttrValueEntity);
                    skuSaleAttrValueEntity.setSkuId(skuInfoEntity.getSkuId());
                    return skuSaleAttrValueEntity;
                }).collect(Collectors.toList());
                skuSaleAttrValueService.saveBatch(skuSaleAttrValueEntities);

                // 保存sku的优惠满减信息
                SkuFullReductionTo fullReductionTo = new SkuFullReductionTo();
                BeanUtils.copyProperties(item,fullReductionTo);
                fullReductionTo.setSkuId(skuInfoEntity.getSkuId());
                // 只有商品优惠的打折折扣或满减价格大于0，才有保存的意义，不然不作保存
                if(fullReductionTo.getFullCount() > 0 || fullReductionTo.getFullPrice().compareTo(new BigDecimal("0")) == 1){
                    R saveFullReduction = couponFeignService.saveInfo(fullReductionTo);
                    if(saveFullReduction.getCode() != 0){
                        log.error("远程调用coupon微服务中sku的优惠满减信息保存功能失败！");
                    }
                }


            });
        }
    }

    @Override
    // 商品上架
    public void up(Long spuId) {
        List<SkuInfoEntity> skuInfoEntities = skuInfoService.up(spuId);
        // 设置每个sku对应的所有可用来检索的规格属性
        // 1、先查询到指定spuId关联的所有规格属性
        List<ProductAttrValueEntity> attrList = productAttrValueService.getBaseAttrListForSpu(spuId);
        List<Long> attrIds = attrList.stream().map(item -> item.getAttrId()).collect(Collectors.toList());
        // 2、根据这些规格属性id，查询到可检索的规格属性id
        List<Long> searchIds = attrService.getSearchAttrIds(attrIds);
        // 3、根据可检索的规格属性id，将第1步获取的所有规格属性进行过滤，并封装成ESModel的Attrs内部类
        List<SkuESModel.Attrs> attrsList = attrList.stream().filter(item -> searchIds.contains(item.getAttrId())).map(item -> {
            SkuESModel.Attrs attrs = new SkuESModel.Attrs();
            BeanUtils.copyProperties(item, attrs);
            return attrs;
        }).collect(Collectors.toList());

        /*
         为避免查询每个skuId是否有库存时，多次调用远程ware服务，可将所有skuId封装成1个List<Long>，
         以POST方式传给ware服务提供的接口，设计业务方法，在ware服务本地查询每个skuId对应的库存容量状态
         然后再根据每个skuId以及对应的库存容量状态重新封装成SkuStockTo，组装成1个R<List<SkuStockTo>>
         传输回来。然后，product服务拿到List<SkuStockTo>，遍历每一项，根据获取的skuId以及对应的库存容量
         封装到1个Map<Long,Boolean>中，最后为每个skuId设置其hasStock状态
         注意：由于远程服务调用存在不稳定问题，会导致转化得到的Map为null，需要加个非空判断！
          */
        List<Long> skuIdList = skuInfoEntities.stream().map(SkuInfoEntity::getSkuId).collect(Collectors.toList());
        Long[] skuIds =  new Long[skuIdList.size()];
        skuIdList.toArray(skuIds);
        Map<Long, Boolean> skuStockMap = null;
        try {
            // 通过TypeReference工具类来实现响应数据的泛型化
            R r = wareFeignService.hasStock(skuIds);
            TypeReference<List<SkuStockTo>> typeReference = new TypeReference<>(){};
            List<SkuStockTo> skuStockTos = r.getData(typeReference);
            // Collectors.toMap方法，参数1：每个元素的key   参数2：每个元素的value
            skuStockMap = skuStockTos.stream().collect(Collectors.toMap(SkuStockTo::getSkuId, sku -> sku.getHasStock()));
        }catch (Exception e){
            // Slf4j提供的日志对象log的占位符使用
            log.error("库存服务远程调用异常，原因为：{}",e.getMessage());
        }

        /*
         由于skuStockMap经过了两次赋值，不符合Lambda表达式中要求变量具有final类型的1次赋值特性（即不可更改性）
         所以，需要重新声明1个新变量，进行赋值及后续操作
          */
        Map<Long, Boolean> finalSkuStockMap = skuStockMap;
        List<SkuESModel> skuESModels = skuInfoEntities.stream().map(skuInfoEntity -> {
            SkuESModel esModel = new SkuESModel();
            BeanUtils.copyProperties(skuInfoEntity, esModel);
            esModel.setSkuPrice(skuInfoEntity.getPrice());
            esModel.setSkuImg(skuInfoEntity.getSkuDefaultImg());
            // TODO 远程调用库存服务，查询是否有库存
            if(finalSkuStockMap == null){
                esModel.setHasStock(true);// 如果获取的map为null，则默认存在库存（网络不稳定问题）
            }else{
                esModel.setHasStock(finalSkuStockMap.get(esModel.getSkuId()));
            }
            // TODO 设置热点评度，新上架商品热度默认为0
            esModel.setHotScore(0L);
            // 根据BrandId查询出指定brand，设置其name和img
            BrandEntity brand = brandService.getById(esModel.getBrandId());
            esModel.setBrandName(brand.getName());
            esModel.setBrandImg(brand.getLogo());
            // 根据catalogId查询出指定catelog，设置其name
            CategoryEntity category = categoryService.getById(esModel.getCatalogId());
            esModel.setCatalogName(category.getName());
            // 设置当前sku的所有可检索的规格属性
            esModel.setAttrs(attrsList);

            // 将根据spuId查询到的skuInfoEntity封装好的esModel收集起来
            return esModel;
        }).collect(Collectors.toList());

        // TODO 再将收集的ESModel集合，发送给guli-mall-es-search服务，然后保存到ES服务器
        R r = esFeignService.saveESModel(skuESModels);
        /*
         这里存在1个bug：ESModel数据成功上传到ES服务器并保存，但spu的status并没有更新
         是因为：前面相关远程服务的调用存在一些超时问题，就比如 库存服务，修改R响应类，不要在
         类上声明泛型，通过TypeReference<T>来实现响应内容的泛型化处理！
          */
        if(r.getCode() == 0){
            // 远程调用成功，则根据spuId去修改对应的publish_status、updateTime
            SpuInfoEntity spuInfoEntity = new SpuInfoEntity();
            spuInfoEntity.setId(spuId);
            spuInfoEntity.setUpdateTime(new Date());
            spuInfoEntity.setPublishStatus(ProductConstant.ProductUpload.SPU_UP.getCode());
            this.baseMapper.updateById(spuInfoEntity);
        }else{
            // 远程调用失败
            // TODO 重复调用？接口幂等性：重试机制？xxx
            // 啥也不作，让其不断重试
        }

    }

    @Override
    public SpuInfoVo getSpuInfoBySkuId(Long skuId) {
        SkuInfoEntity skuInfoEntity = skuInfoService.getById(skuId);
        BrandEntity brandEntity = brandService.getById(skuInfoEntity.getBrandId());
        Long spuId = skuInfoEntity.getSpuId();
        SpuInfoEntity spuInfoEntity = this.getById(spuId);
        SpuInfoVo spuInfoVo = new SpuInfoVo();
        BeanUtils.copyProperties(spuInfoEntity,spuInfoVo);
        spuInfoVo.setBrandName(brandEntity.getName());
        return spuInfoVo;
    }

}